#ifndef _MATHEMATICS_ALGEBRA_FRACTION_
#define _MATHEMATICS_ALGEBRA_FRACTION_

#include "math_helper.h"
#include "..\..\utilities\string_helper.h"
using namespace science::utilities;

#include <iostream>
#include <vector>
#include <algorithm>
#include <stdexcept>

namespace science
{
	namespace mathematics
	{
		namespace algebra
		{
			class fraction
			{
			private:
				int _numerator;
				int _denomenator;

			public:
				fraction()
					: _numerator(0)
					, _denomenator(1)
				{

				}

				fraction(int numerator)
					: _numerator(numerator)
					, _denomenator(1)
				{

				}

				fraction(std::string fraction_str)
				{
					unsigned int index = 0;
					*this = parse_fraction(fraction_str, index);
				}

				fraction(int numerator, int denomenator)
					: _numerator(numerator)
				{
					if (denomenator == 0)
						throw new std::invalid_argument("The denominator cannot be zero");
					_denomenator = denomenator;
				}

				~fraction() {}

			public:
				fraction simplify()
				{
					return fraction::simplify(this->_numerator, this->_denomenator);
				}

				fraction reciprocal()
				{
					if (_numerator == 0)
						throw new std::invalid_argument("The denominator cannot be zero");
					return fraction(this->_denomenator, this->_numerator);
				}

			public:

				int get_numerator() const { return _numerator; }
				int get_denomenator() const { return _denomenator; }
				void set_numerator(int numerator) { _numerator = numerator; *this = simplify(); }
				void set_denomenator(int denomenator)
				{
					if (denomenator == 0)
						throw new std::invalid_argument("The denominator cannot be zero");
					_denomenator = denomenator; *this = simplify();
				}				

			public:

				bool operator==(const fraction& frc) const
				{
					fraction t = fraction::simplify(*this);
					fraction t2 = fraction::simplify(frc);
					return t._numerator == t2.get_numerator() && t._denomenator == t2.get_denomenator();
				}

				bool operator!=(const fraction& frc) const
				{
					return !(this->operator==(frc));
				}

				bool operator==(const fraction& frc)
				{
					return (const_cast<const fraction*>(this))->operator==(frc);
				}

				bool operator!=(const fraction& frc)
				{
					return (const_cast<const fraction*>(this))->operator!=(frc);
				}

				fraction& operator+=(const fraction& rhs)
				{
					*this = this->operator+(rhs);
					return *this;
				}

				fraction operator+(const fraction& rhs)
				{
					return (const_cast<const fraction*>(this))->operator+(rhs);
				}

				fraction operator+(const fraction& rhs) const
				{
					fraction rsult;
					if (_denomenator == rhs._denomenator)
						rsult = fraction(_numerator + rhs._numerator, _denomenator);
					else
						rsult = fraction((_numerator * rhs._denomenator) + (rhs._numerator * _denomenator),
						_denomenator * rhs._denomenator);
					return rsult.simplify();
				}

				fraction& operator-=(const fraction& rhs)
				{
					*this = this->operator-(rhs);
					return *this;
				}

				fraction operator-(const fraction& rhs)
				{
					return (const_cast<const fraction*>(this))->operator-(rhs);
				}

				fraction operator-(const fraction& rhs) const
				{
					fraction rsult;
					if (_denomenator == rhs._denomenator)
						rsult = fraction(_numerator - rhs._numerator, _denomenator);
					else
						rsult = fraction((_numerator * rhs._denomenator) - (rhs._numerator * _denomenator),
						_denomenator * rhs._denomenator);
					return rsult.simplify();
				}

				fraction& operator*=(int num)
				{
					*this = this->operator*(num);
					return *this;
				}

				fraction operator*(int num)
				{
					return (const_cast<const fraction*>(this))->operator*(num);
				}

				fraction operator*(int num) const
				{
					fraction result = fraction(_numerator * num, _denomenator * num);
					return result.simplify();
				}

				fraction& operator*=(const fraction& rhs)
				{
					*this = this->operator*(rhs);
					return *this;
				}

				fraction operator*(const fraction& rhs)
				{
					return (const_cast<const fraction*>(this))->operator*(rhs);
				}

				fraction operator*(const fraction& rhs) const
				{
					fraction result = fraction(_numerator * rhs._numerator, _denomenator * rhs._denomenator);
					return result.simplify();
				}

				fraction& operator/=(int num)
				{
					*this = this->operator/(num);
					return *this;
				}

				fraction operator/(int num)
				{
					return (const_cast<const fraction*>(this))->operator/(num);
				}

				fraction operator/(int num) const
				{
					if (num == 0)
						throw new std::invalid_argument("Cannot divide by zero");

					fraction result = fraction(_numerator / num, _denomenator / num);
					return result.simplify();
				}

				fraction& operator/=(const fraction& rhs)
				{
					*this = this->operator/(rhs);
					return *this;
				}

				fraction operator/(const fraction& rhs)
				{
					return (const_cast<const fraction*>(this))->operator/(rhs);
				}

				fraction operator/(const fraction& rhs) const
				{
					fraction result = fraction(_numerator * rhs._denomenator, _denomenator * rhs._numerator);
					return result.simplify();
				}

			public:
				static fraction simplify(const fraction& frc)
				{
					return fraction::simplify(frc._numerator, frc._denomenator);
				}

				static fraction simplify(int numerator, int denomenator)
				{
					if (denomenator == 0)
						throw new std::invalid_argument("The denominator cannot be zero");

					int gcf = math_helper::gcf(numerator, denomenator);
					fraction frc(numerator / gcf, denomenator / gcf);
					return frc;
				}

				static fraction parse_fraction(std::string str, unsigned int& index)
				{
					str = string_helper::remove_white_chars(str);
					fraction frc;

					if (index >= str.size())
						return frc;

					frc.set_numerator(string_helper::parse_int(str, index));
					if (index >= str.size())
						return frc;

					if (str[index] != '/')
						throw new std::invalid_argument("Invalid fraction string");
					index++;
					if (index >= str.size())
						throw new std::invalid_argument("Invalid fraction string");

					frc.set_denomenator(string_helper::parse_int(str, index));
					index++;
					return frc;
				}

			public:
				friend std::ostream &operator<<(std::ostream &output, const fraction &frc)
				{
					output << frc._numerator << "/" << frc._denomenator;
					return output;
				}

				friend std::istream &operator>>(std::istream  &input, fraction &frc)
				{
					char separator;
					int n, d;
					input >> n >> separator >> d;
					if (separator != '/')
						throw new std::invalid_argument("Invalid fraction");
					frc = fraction(n, d);
					return input;
				}
			};

			static fraction operator*(int num, const fraction& rhs)
			{
				return rhs.operator*(num);
			}

			static fraction operator/(int num, const fraction& rhs)
			{
				return rhs.operator/(num);
			}
		}
	}
}

#endif